package cn.donting.jme.editor.file;

import cn.donting.jme.project.Project;
import cn.donting.jme.project.gradle.GradleProject;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.watch.WatchMonitor;
import cn.hutool.core.io.watch.Watcher;
import cn.hutool.core.util.HashUtil;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.jme3.asset.DesktopAssetManager;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.WatchEvent;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
public class ProjectFileMetaService implements ListenerListener {
    private static final ObjectMapper objectMapper = new ObjectMapper();

    static {// 启用美化输出，添加缩进和换行
        objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
    }

    private final File metaInfoFile;
    private final Project project;

    public ProjectFileMetaService(Project project) throws Exception {
        this.project = project;
        this.metaInfoFile = new File(project.getProjectDir(), ".editor/fileMeta.json");
        if (!this.metaInfoFile.exists()) {
            initFileMeta();
        }
        //这里只监听文件或目录的修改事件
        WatchMonitor watchMonitor = WatchMonitor.create(project.getSrcDir(), WatchMonitor.EVENTS_ALL);
        watchMonitor.setWatcher(new Watcher() {
            @Override
            public void onCreate(WatchEvent<?> event, Path currentPath) {
                try {
                    checkFileChange();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }

            @Override
            public void onModify(WatchEvent<?> event, Path currentPath) {
            }

            @Override
            public void onDelete(WatchEvent<?> event, Path currentPath) {
                try {
                    checkFileChange();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }

            @Override
            public void onOverflow(WatchEvent<?> event, Path currentPath) {
            }
        });

        //设置监听目录的最大深入，目录层级大于制定层级的变更将不被监听，默认只监听当前层级目录
        watchMonitor.setMaxDepth(30);
        //启动监听
        watchMonitor.start();
        log.info("watchMonitor 开始");
    }


    private void initFileMeta() throws Exception {
        HashMap<String, FileMeta> fileMeta = createFileMeta();
        objectMapper.writeValue(metaInfoFile, fileMeta);
    }

    private HashMap<String, FileMeta> createFileMeta() throws Exception {
        File srcDir = project.getSrcDir();
        HashMap<String, FileMeta> fileMetaHashMap = new HashMap<>();
        for (File file : srcDir.listFiles()) {
            createFileMeta(srcDir, file, fileMetaHashMap);
        }
        return fileMetaHashMap;
    }

    private void createFileMeta(File rootFile, File file, HashMap<String, FileMeta> fileMetas) throws Exception {
        if (file.isDirectory()) {
            for (File listFile : file.listFiles()) {
                createFileMeta(rootFile, listFile, fileMetas);
            }
            return;
        }
        FileMeta fileMeta = createFileMeta(rootFile, file);
        fileMetas.put(fileMeta.getFilePath(), fileMeta);
    }

    private FileMeta createFileMeta(File rootFile, File file) throws IOException {
        String filePath = FileUtil.subPath(rootFile.getCanonicalPath(), file.getCanonicalPath());
        FileMeta fileMeta = new FileMeta();
        fileMeta.setFilePath(filePath);
        fileMeta.setLength(file.length());
        fileMeta.setLastModifyTime(file.lastModified());
        fileMeta.setHash(fileHash(file));
        return fileMeta;
    }

    private int fileHash(File file) throws IOException {
        long length = file.length();
        if (length <= 2048) {
            byte[] bytes = FileUtil.readBytes(file);
            int i = HashUtil.fnvHash(bytes);
            return i;
        }
        int headerSize = 512; // 读取的头部字节数
        int centerSize = 512; // 读取的头部字节数
        int footerSize = 512; // 读取的尾部字节数
        try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
             FileChannel fileChannel = randomAccessFile.getChannel()) {

            // 读取头部字节
            ByteBuffer headerBuffer = ByteBuffer.allocate(headerSize);
            fileChannel.read(headerBuffer);
            headerBuffer.flip();
            // 获取文件长度
            long fileSize = fileChannel.size();

            long centerStartPosition = Math.max(0, fileSize / 2 - centerSize / 2);
            fileChannel.position(centerStartPosition);
            // 读取尾部字节
            ByteBuffer centerBuffer = ByteBuffer.allocate(footerSize);
            fileChannel.read(centerBuffer);
            headerBuffer.flip();

            // 计算尾部的起始位置
            long footerStartPosition = Math.max(0, fileSize - footerSize);
            // 移动文件指针到尾部的起始位置
            fileChannel.position(footerStartPosition);

            // 读取尾部字节
            ByteBuffer footerBuffer = ByteBuffer.allocate(footerSize);
            fileChannel.read(footerBuffer);
            footerBuffer.flip();

            int headerHash = HashUtil.fnvHash(headerBuffer.array());
            int centerHash = HashUtil.fnvHash(centerBuffer.array());
            int footerHash = HashUtil.fnvHash(footerBuffer.array());
            return headerHash + centerHash + footerHash;
        } catch (IOException e) {
            e.printStackTrace();
            throw e;
        }
    }

    private void checkFileChange() throws Exception {
        log.info("checkFileChange");
        HashMap<String, FileMeta> oldFileMetasMap = objectMapper.readValue(metaInfoFile, new TypeReference<HashMap<String, FileMeta>>() {
        });
        HashMap<String, FileMeta> newFileMetaMap = createFileMeta();
        objectMapper.writeValue(metaInfoFile, newFileMetaMap);
        Map<String, FileMeta> deleteFileMap = new HashMap<>();
        Map<String, FileMeta> newFileMap = new HashMap<>();

        for (FileMeta newFileMeta : newFileMetaMap.values()) {
            FileMeta oldFileMeta = oldFileMetasMap.get(newFileMeta.getFilePath());
            if (oldFileMeta == null) {
                newFileMap.put(newFileMeta.getFilePath(), newFileMeta);
                continue;
            }
            //有变化
            if (!oldFileMeta.equals(newFileMeta)) {
                continue;
            }
        }
        for (FileMeta oldFileMeta : oldFileMetasMap.values()) {
            FileMeta newFileMeta = newFileMetaMap.get(oldFileMeta.getFilePath());
            //删除的文件
            if (newFileMeta == null) {
                deleteFileMap.put(oldFileMeta.getFilePath(), oldFileMeta);
                continue;
            }else {
                newFileMetaMap.remove(newFileMeta.getFilePath());
            }
        }



        List<FileMeta> deleteFiles = deleteFileMap.values().stream().collect(Collectors.toList());
        List<FileMeta> newFiles = newFileMap.values().stream().collect(Collectors.toList());
        for (FileMeta deleteFileMeta : deleteFiles) {
            for (FileMeta newFile : newFiles) {
                if (newFile.isRename(deleteFileMeta)) {
                    // 移动/重命名
                    deleteFileMap.remove(deleteFileMeta.getFilePath());
                    newFileMetaMap.remove(newFile.getFilePath());
                    renameFile(deleteFileMeta, newFile, this.project.getSrcDir());
                }
            }
        }
        for (FileMeta value : deleteFileMap.values()) {
            deleteFile(value, this.project.getSrcDir());
        }
        for (FileMeta value : newFileMetaMap.values()) {
            newFile(value, this.project.getSrcDir());
        }
    }


    @Override
    public void deleteFile(FileMeta fileMeta, File rootFileDir) {
        log.info("deleteFile:" + fileMeta.getFilePath());
    }

    @Override
    public void newFile(FileMeta fileMeta, File rootFileDir) {
        log.info("newFile:" + fileMeta.getFilePath());
    }

    @Override
    public void renameFile(FileMeta oldFileMeta, FileMeta newFile, File rootFileDir) {
        log.info("renameFile:{}->{}", oldFileMeta.getFilePath(), newFile.getFilePath());
    }


    public static void main(String[] args) throws Exception {
        Project project1 = new GradleProject(new File("./jme-template"));
        ProjectFileMetaService projectFileMetaService = new ProjectFileMetaService(project1);
    }
}
